package org.example.chat.utils;


import cn.hutool.json.JSONUtil;
import org.example.chat.api.Constant;
import org.example.chat.config.ServerConfigProperties;
import org.example.chat.entity.RedisData;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * @author cheval
 */
@Component
public class CommonAlgorithm {

    @Autowired
    public StringRedisTemplate redisTemplate;
    @Autowired
    protected RedissonClient redissonClient;
    @Autowired
    protected ServerConfigProperties.CacheProperties cacheProperties;

    /**
     * 互斥查询 redis
     * 防止缓存穿透： 缓存null值，
     * 防止缓存击穿： 互斥锁方式
     * @param key 缓存key
     * @param dataClass 缓存数据类型
     * @param queryMethod 查询数据库逻辑
     * @param param 查询数据库函数queryMethod需要的参数
     * @return
     */
    public <T, P> List<T> mutexQueryCache(String key, Class<T> dataClass, Function<P, List<T>> queryMethod, P param) {
        // 1. 首先从缓存中获取
        String dataStr = redisTemplate.opsForValue().get(key);
        List<T> data = null;
        if (dataStr != null) {
            data = JSONUtil.toList(dataStr, dataClass);
            return data;
        }
        // 2. 缓存中不存在，则从数据库中获取， 为了防止缓存击穿，保证只有一个线程可以查询数据库
        String lockName = Constant.LOCK_PREFIX + "mutex_query:" + key;
        // 获取互斥查询锁
        RLock lock = redissonClient.getLock(lockName);
        boolean isLock = lock.tryLock();
        if (isLock) {
            // 获得锁, 查询数据库
            data = queryMethod.apply(param);
            // 将数据缓存到redis
            redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(data), cacheProperties.getCacheExpireTime(), TimeUnit.MINUTES);
            // 释放锁
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
            return data;
        } else {
            // 没有获取到锁，则等待一段时间，重复上面操作
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return mutexQueryCache(key, dataClass, queryMethod, param);
        }
    }

    public <T, P> List<T> logicalExpirationQuery(String key, Class<T> dataClass, Function<P, List<T>> queryMethod, P param) {
        // 1. 先查询缓存， 如果缓存中没有数据或缓存中的数据已经过期
        String dataStr = redisTemplate.opsForValue().get(key);
        if (dataStr == null || JSONUtil.toBean(dataStr, RedisData.class).getExpireTime().isBefore(LocalDateTime.now())) {
            // 2. 缓存中没有数据，或则逻辑过期,则查询数据库
            String lockName = Constant.LOCK_PREFIX + "logical_query:" + key;
            RLock lock = redissonClient.getLock(lockName);
            boolean isLock = lock.tryLock();
            if (isLock) {
                // 查询数据库
                List<T> data = queryMethod.apply(param);
                // 缓存数据
                RedisData redisData = new RedisData();
                redisData.setExpireTime(LocalDateTime.now().plusMinutes(cacheProperties.getCacheExpireTime()));
                redisData.setData(data);
                redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
                // 释放锁
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
                return data;
            }
        }
        // 3. 直接返回旧数据
        return dataStr == null ? null : JSONUtil.toList(JSONUtil.toJsonStr(JSONUtil.toBean(dataStr, RedisData.class).getData()), dataClass);
    }
}
